﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using exsportance.Silverlight.Validate;
using exsportance.Silverlight.Forms.Helpers;
using exsportance.Silverlight.Common.Utils;
using exsportance.Silverlight.Common.Helper;

namespace exsportance.Silverlight.Forms
{
    /// <summary>
    /// A form which contains many FormElements
    /// </summary>
    public partial class Form : UserControl
    {
        #region members
        private List<FormElement> _elements;
        private Validate.ValidationGroup _validationGroup;

        private ValidationGroup _tmpGroup = null;
        private ValidationGroupMode _tmpGroupMode = ValidationGroupMode.And;
        #endregion

        #region properties
        /// <summary>
        /// Gets or sets the width of the label column
        /// </summary>
        public double LabelColumnWidth
        {
            get { return getLabelColumnWidth(); }
            set { setLabelColumnWidth(value); }
        }

        /// <summary>
        /// Gets or sets the width of the control column
        /// </summary>
        public double ControlColumnWidth
        {
            get { return getControlColumnWidth(); }
            set { setControlColumnWidth(value); }
        }

        /// <summary>
        /// Element container
        /// </summary>
        public List<FormElement> Elements
        {
            get { return _elements; }
        }
        #endregion

        #region constructor
        public Form()
        {
            InitializeComponent();

            _elements = new List<FormElement>();
            _validationGroup = new exsportance.Silverlight.Validate.ValidationGroup();

            this.Loaded += new RoutedEventHandler(Form_Loaded);
        }
        #endregion

        #region public methods
        /// <summary>
        /// Validates all ValidationItems and shows the errors
        /// </summary>
        /// <returns></returns>
        public ValidationResult Validate()
        {
            ValidationResult result = _validationGroup.Validate();

            showMessages(result);

            return result;
        }

        /// <summary>
        /// Validates all ValidationItems. No errors are shown
        /// </summary>
        /// <returns></returns>
        public bool IsValid()
        {
            return _validationGroup.IsValid();
        }

        /// <summary>
        /// Begin a new group
        /// </summary>
        /// <param name="groupName"></param>
        public void BeginGroup(string groupName)
        {
            BeginGroup(groupName, ValidationGroupMode.And);
        }

        /// <summary>
        /// Begin a new group
        /// </summary>
        /// <param name="groupName"></param>
        /// <param name="validationGroupMode"></param>
        public void BeginGroup(string groupName, ValidationGroupMode validationGroupMode)
        {
            _tmpGroupMode = validationGroupMode;
            _tmpGroup = new ValidationGroup(groupName);
        }

        /// <summary>
        /// Finish a group
        /// </summary>
        public void EndGroup()
        {
            if (_tmpGroup == null)
                throw new Exception("No open group found! Call BeginGroup first.");

            _validationGroup.Add(_tmpGroup, _tmpGroupMode);

            _tmpGroup = null;
        }

        /// <summary>
        /// Adds a new element to form
        /// </summary>
        /// <param name="formElement"></param>
        /// <returns></returns>
        public FormElement AddElement(FormElement formElement)
        {
            _elements.Add(formElement);

            // create the row with controls
            createRow(formElement);

            return formElement;
        }

        /// <summary>
        /// Adds a new element to form
        /// </summary>
        /// <param name="name"></param>
        /// <param name="label"></param>
        /// <param name="control"></param>
        /// <param name="valueProperty"></param>
        /// <returns></returns>
        public FormElement AddElement(string name, string label, FrameworkElement control, DependencyProperty valueProperty)
        {
            return AddElement(new FormElement(this, name, label, control, valueProperty));
        }

        /// <summary>
        /// Adds a new element to form
        /// </summary>
        /// <param name="name"></param>
        /// <param name="label"></param>
        /// <param name="control"></param>
        /// <param name="valueProperty"></param>
        /// <param name="initialValue"></param>
        /// <returns></returns>
        public FormElement AddElement(string name, string label, FrameworkElement control, DependencyProperty valueProperty, object initialValue)
        {
            return AddElement(new FormElement(this, name, label, control, valueProperty, initialValue));
        }

        /// <summary>
        /// Adds a new element to form
        /// </summary>
        /// <param name="name"></param>
        /// <param name="label"></param>
        /// <param name="control"></param>
        /// <param name="valueProperty"></param>
        /// <param name="initialValue"></param>
        /// <param name="validator"></param>
        /// <returns></returns>
        public FormElement AddElement(string name, string label, FrameworkElement control, DependencyProperty valueProperty, object initialValue, params IValidator[] validators)
        {
            FormElement element = GetElement(name);
            if (element != null)
                throw new ArgumentException("Name '{0}' already exists!".Frmt(name));

            element = AddElement(new FormElement(this, name, label, control, valueProperty, initialValue));

            addValidator(control, valueProperty, validators);

            return element;
        }

        /// <summary>
        /// Adds a new textbox
        /// </summary>
        /// <param name="name"></param>
        /// <param name="label"></param>
        /// <param name="validators"></param>
        /// <returns></returns>
        public FormElement AddTextBox(string name, string label, params IValidator[] validators)
        {
            TextBox textBox = new TextBox();
            textBox.Height = 22;

            return AddElement(name, label, textBox, TextBox.TextProperty, "", validators);
        }

        /// <summary>
        /// Adds a new PasswordBox
        /// </summary>
        /// <param name="name"></param>
        /// <param name="label"></param>
        /// <param name="validators"></param>
        /// <returns></returns>
        public FormElement AddPasswordBox(string name, string label, params IValidator[] validators)
        {
            PasswordBox box = new PasswordBox();
            PasswordBoxAssistant.SetBindPassword(box, true);
            box.Height = 22;

            return AddElement(name, label, box, PasswordBoxAssistant.BoundPassword, "", validators);
        }

        /// <summary>
        /// Adds a TextBlock
        /// </summary>
        /// <param name="name"></param>
        /// <param name="label"></param>
        /// <param name="labelContent"></param>
        /// <param name="bold"></param>
        /// <returns></returns>
        public FormElement AddTextBlock(string name, string label, string text, bool bold)
        {
            TextBlock labelControl = new TextBlock();
            labelControl.Text = text;
            labelControl.Height = double.NaN;
            labelControl.Width = double.NaN;
            if (bold)
                labelControl.FontWeight = FontWeights.Bold;

            return AddElement(name, label, labelControl, TextBlock.TextProperty, null, null);
        }

        /// <summary>
        /// Add a new empty row
        /// </summary>
        public void AddSpacer()
        {
            AddTextBlock("space_" + _elements.Count, "", "", false);
        }

        /// <summary>
        /// Add a submit button
        /// </summary>
        /// <param name="name"></param>
        /// <param name="text"></param>
        public void AddSubmitButton(string name, string text, RoutedEventHandler clickHandler)
        {
            AddSpacer();
            Button button = new Button();
            button.Content = text;
            button.Click += clickHandler;
            button.Width = 80;
            button.Height = 21;
            button.HorizontalAlignment = HorizontalAlignment.Left;

            AddElement(name, "", button, Button.ContentProperty);
        }

        /// <summary>
        /// Adds a new validator
        /// </summary>
        /// <param name="validator"></param>
        /// <param name="control"></param>
        /// <param name="valueProperty"></param>
        /// <returns></returns>
        public IValidation AddValidator(IValidator validator, FrameworkElement control, DependencyProperty valueProperty)
        {
            ValidationItem item = new ValidationItem(validator, control, valueProperty);

            if (_tmpGroup == null)
                _validationGroup.Add(item);
            else
                _tmpGroup.Add(item);

            return item;
        }

        /// <summary>
        /// Add a new ValidationGroup for this control
        /// </summary>
        /// <param name="control"></param>
        /// <param name="valueProperty"></param>
        /// <param name="validators"></param>
        private void addValidator(FrameworkElement control, DependencyProperty valueProperty, params IValidator[] validators)
        {
            if (validators != null)
            {
                ValidationGroup group = new ValidationGroup();

                foreach (IValidator validator in validators)
                    group.Add(new ValidationItem(validator, control, valueProperty));

                if (_tmpGroup == null)
                    _validationGroup.Add(group);
                else
                    _tmpGroup.Add(group);
            }
        }

        /// <summary>
        /// Adds a new ValidationGroup for this control
        /// </summary>
        /// <param name="elementName"></param>
        /// <param name="validators"></param>
        public void AddValidator(string elementName, params IValidator[] validators)
        {
            FormElement element = GetElement(elementName);

            if (element != null)
                addValidator(element.Control, element.ValueProperty, validators);
        }

        /// <summary>
        /// Add new validator(s).Item or Group
        /// </summary>
        /// <param name="items"></param>
        public void AddValidator(params IValidation[] items)
        {
            if (_tmpGroup == null)
                _validationGroup.Add(items);
            else
                _tmpGroup.Add(items);
        }

        /// <summary>
        /// Add a new validator. Item or Group
        /// </summary>
        /// <param name="validationItem"></param>
        /// <param name="mode"></param>
        public void AddValidator(IValidation validationItem, ValidationGroupMode mode)
        {
            if (_tmpGroup == null)
                _validationGroup.Add(validationItem, mode);
            else
                _tmpGroup.Add(validationItem, mode);
        }

        /// <summary>
        /// Get a element by name
        /// </summary>
        /// <param name="elementName"></param>
        /// <returns></returns>
        public FormElement GetElement(string elementName)
        {
            return _elements.SingleOrDefault(e => e.Name == elementName);
        }

        /// <summary>
        /// Disable all controls
        /// </summary>
        public void DisableControls()
        {
            setControlsEnable(false);
        }

        /// <summary>
        /// Enable all controls
        /// </summary>
        public void EnableControls()
        {
            setControlsEnable(true);
        }

        /// <summary>
        /// Add a control to the main grid
        /// </summary>
        /// <param name="element"></param>
        /// <param name="columnIndex"></param>
        /// <param name="rowIndex"></param>
        public void AddToGrid(UIElement element, int columnIndex, int rowIndex)
        {
            LayoutRoot.Children.Add(element);
            element.SetValue(Grid.ColumnProperty, columnIndex);
            element.SetValue(Grid.RowProperty, rowIndex);
        }

        /// <summary>
        /// Get the TextBlock which contains the label
        /// </summary>
        /// <param name="elementName"></param>
        /// <returns></returns>
        public TextBlock GetLabelControl(string elementName)
        {
            return GetLabelControl(GetElement(elementName));
        }

        /// <summary>
        /// Get the TextBlock which contains the label
        /// </summary>
        /// <param name="elementName"></param>
        /// <returns></returns>
        public TextBlock GetLabelControl(FormElement element)
        {
            if (element == null)
                throw new ArgumentNullException("element");

            TextBlock result = null;

            object obj = LayoutRoot.FindName("label_" + element.Name);
            if (obj != null)
                result = obj as TextBlock;

            return result;
        }

        /// <summary>
        /// Get the value of a form element
        /// </summary>
        /// <param name="elementName">Name of element</param>
        /// <returns></returns>
        public object GetElementValue(string elementName)
        {
            object result = null;
            FormElement element = GetElement(elementName);

            if (element != null)
                result = element.Value;

            return result;
        }
        #endregion


        #region private methods
        private void setControlsEnable(bool enable)
        {
            foreach (FormElement element in _elements)
            {
                if (element.Control is Control)
                    (element.Control as Control).IsEnabled = enable;
            }
        }

        private void setLabelColumnWidth(double value)
        {
            LayoutRoot.ColumnDefinitions[1].Width = new GridLength(value, GridUnitType.Pixel);
        }

        private double getLabelColumnWidth()
        {
            return LayoutRoot.ColumnDefinitions[1].Width.Value;
        }

        private void setControlColumnWidth(double value)
        {
            LayoutRoot.ColumnDefinitions[2].Width = new GridLength(value, GridUnitType.Pixel);
        }

        private double getControlColumnWidth()
        {
            return LayoutRoot.ColumnDefinitions[2].Width.Value;
        }

        void Form_Loaded(object sender, RoutedEventArgs e)
        {
            // set focus to first control
            if (_elements.Count > 0)
            {
                if (_elements[0].Control is Control)
                    (_elements[0].Control as Control).Focus();
            }
        }

        private void createRow(FormElement element)
        {
            // create initial row definiion
            if (LayoutRoot.RowDefinitions.Count == 0)
                addInitialRowDefinition();

            // add a new to to grid
            addNewRowDefinition(element.Control);

            // create label control
            addLabel(element);

            // set control to grid
            LayoutRoot.Children.Add(element.Control);
            element.Control.SetValue(Grid.ColumnProperty, 3);
            element.Control.SetValue(Grid.RowProperty, LayoutRoot.RowDefinitions.Count - 2);

            // add the error textblock
            addErrorRow(element);

            // add a space row
            addSpaceRowDefinition();
        }

        private void addLabel(FormElement element)
        {
            TextBlock label = new TextBlock();
            label.Name = "label_" + element.Name;
            label.Text = element.Label;
            label.TextAlignment = TextAlignment.Right;
            label.HorizontalAlignment = HorizontalAlignment.Right;
            label.Height = double.NaN;
            label.Width = double.NaN;
            label.FontWeight = FontWeights.Bold;
            label.VerticalAlignment = VerticalAlignment.Center;

            LayoutRoot.Children.Add(label);
            label.SetValue(Grid.ColumnProperty, 1);
            label.SetValue(Grid.RowProperty, LayoutRoot.RowDefinitions.Count - 2);
        }

        private void addNewRowDefinition(FrameworkElement control)
        {
            double height = control.ActualHeight;
            if (height.IsNaN() || height == 0)
                height = control.Height;
            if (height.IsNaN() || height == 0)
                height = 30;

            RowDefinition definition = new RowDefinition();
            definition.Height = new GridLength(height);
            // insert before last definition cause last definiion is only to handle empty space
            LayoutRoot.RowDefinitions.Insert(LayoutRoot.RowDefinitions.Count - 1, definition);
        }

        private void addSpaceRowDefinition()
        {
            RowDefinition definition = new RowDefinition();
            definition.Height = new GridLength(5);
            // insert before last definition cause last definiion is only to handle empty space
            LayoutRoot.RowDefinitions.Insert(LayoutRoot.RowDefinitions.Count - 1, definition);
        }

        private void addInitialRowDefinition()
        {
            RowDefinition definition = new RowDefinition();
            definition.Height = GridLength.Auto;
            LayoutRoot.RowDefinitions.Add(definition);
        }

        private void addErrorRow(FormElement element)
        {
            // add row
            RowDefinition definition = new RowDefinition();
            definition.Height = new GridLength(0);
            // insert before last definition cause last definiion is only to handle empty space
            LayoutRoot.RowDefinitions.Insert(LayoutRoot.RowDefinitions.Count - 1, definition);

            // add border
            Border border = new Border();
            border.Height = double.NaN;
            border.Width = double.NaN;
            border.Background = new SolidColorBrush(Color.FromArgb(200, 255, 119, 128));
            border.BorderThickness = new Thickness(1);
            border.BorderBrush = new SolidColorBrush(Color.FromArgb(255, 255, 20, 30));
            border.Margin = new Thickness(0, 2, 0, 0);
            LayoutRoot.Children.Add(border);

            border.SetValue(Grid.ColumnProperty, 3);
            border.SetValue(Grid.RowProperty, LayoutRoot.RowDefinitions.Count - 2);

            // add textblock
            TextBlock label = new TextBlock();
            label.Name = element.Name + "_errorBlock";
            label.Height = double.NaN;
            label.Width = double.NaN;
            label.TextWrapping = TextWrapping.Wrap;
            border.Child = label;
        }

        private void showMessages(ValidationResult result)
        {
            int c = 1;
            foreach (FormElement element in _elements)
            {
                // get messages for this control
                ValidationMessages messages = result.GetMessages(element.Control);

                //if (messages.Count > 0)
                //{
                //    FormBalloonTip balloon = new FormBalloonTip("Error", messages.ToString());
                //    Panel panel = Application.Current.RootVisual as Panel;
                //    balloon.LayoutRoot.Opacity = 0;
                //    panel.Children.Add(balloon);
                //    Point p = Position.GetRelativePosition(element.Control, panel);

                //    balloon.VerticalAlignment = VerticalAlignment.Top;
                //    balloon.HorizontalAlignment = HorizontalAlignment.Left;

                //    balloon.Margin = new Thickness(p.X, p.Y + element.Control.ActualHeight, 0, 0);
                //    // balloon.Measure(new Size(element.Control.ActualWidth, 500));
                //    balloon.Width = 200;
                //    balloon.Height = 30;
                //    balloon.fadeIn.Begin();

                //    element.BalloonTip = balloon;
                //}
                //else
                //{
                //    if (element.BalloonTip != null)
                //    {
                //        Panel panel = Application.Current.RootVisual as Panel;
                //        element.BalloonTip.fadeOut.Begin();
                //        panel.Children.Remove(element.BalloonTip);
                //        element.BalloonTip = null;
                //    }
                //}

                // get errorblock
                object obj = LayoutRoot.FindName(element.Name + "_errorBlock");
                if (obj != null)
                {
                    TextBlock label = obj as TextBlock;
                    label.Text = messages.ToString();
                    label.Margin = new Thickness(2);

                    if (messages.Count > 0)
                    {
                        LayoutRoot.RowDefinitions[c].Height = new GridLength(label.ActualHeight);
                    }
                    else
                        LayoutRoot.RowDefinitions[c].Height = new GridLength(0);
                }
                c += 3;
            }

            LayoutRoot.UpdateLayout();
        }
        #endregion
    }
}
